<template>
	<div class="container">
		<div class="user-info">
			<el-descriptions title="个人信息">
			    <el-descriptions-item label="用户名">{{ formInline.nickname }}</el-descriptions-item>
			    <el-descriptions-item label="唯一身份识别">{{ formInline.userId }}</el-descriptions-item>
			    <el-descriptions-item label="房间号">{{ formInline.roomId }}</el-descriptions-item>
			    <el-descriptions-item label="是否是发布者">{{ formInline.pub === 'pub' ? 'Yes' : 'No' }}</el-descriptions-item>
			</el-descriptions>
		</div>
		<div class="user-list">
			<el-table :data="roomUserList" border style="width: 100%">
				<el-table-column prop="userId" label="userId"></el-table-column>
				<el-table-column prop="nickname" label="nickname"></el-table-column>
				<el-table-column prop="roomId" label="room"></el-table-column>
				<el-table-column prop="pub" label="publisher"></el-table-column>
			</el-table>
		</div>
		<div class="video-container">
			<video v-if="formInline.pub !== 'pub'" class="remote-video" ref="remoteVideoRef" autoplay controls muted></video>
			<video v-if="formInline.pub === 'pub'" class="local-video" ref="localVideoRef" autoplay controls muted></video>
		</div>
	</div>
</template>

<script>
    import { io } from 'socket.io-client'
	export default {
		name: 'One2Many',
		data() {
			return {
				// 实例
				peerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection,
				rtcPcMaps: new Map(),
				dataChannelMap: new Map(),
				linkSocket: undefined,
				localRtcPc: undefined,
				// 数据
				localDevice: {
					audioIn:[],
					videoIn: [],
					audioOut: []
				},
                serverSocketUrl: 'ws://127.0.0.1:18080',
				rtcPcParams: {
                    iceServers: [
                        // 谷歌的公共服务
                        // { url: "stun:stun.l.google.com:19302"}
                    ]
				},
				roomUserList: [],
				formInline: {
					videoId: undefined,
					audioInId: undefined,
					nickname: undefined,
					roomId: undefined,
					pub: undefined,
				},
				publisher: undefined,
			}
		},
		created() {
			this.initFormInLineInfo(() => {
				this.initSignalService()
			})		
			this.initInnerLocalDevice()
		},
		methods:{
			initFormInLineInfo(callback) {
				this.formInline.nickname = this.getUrlParams("nickname");
				this.formInline.roomId = this.getUrlParams("roomId");
				this.formInline.userId = this.getUrlParams("userId");
				this.formInline.pub = this.getUrlParams("pub") ? this.getUrlParams("pub") : 'no';
				if (this.formInline.nickname && this.formInline.roomId && this.formInline.userId) {
					callback()
				}
			},
			initSignalService() {
				this.linkSocket = io(this.serverSocketUrl, {
                    // 最大重连延迟时间
					reconnectionDelayMax: 10000,
                    // 传输方式
					transports: ["websocket"],
					query: this.formInline
				});
				this.linkSocket.on("roomUserList", async (e) => {
					this.roomUserList = typeof e === 'object' ? Object.values(e).map(item => JSON.parse(item)) : []
					if (this.roomUserList.length) {
						await this.initMeetingRoomPc()
					}
				})
				this.linkSocket.on("msg", async (e) => {
					console.log("receive msg => ", e)
                    const type = e && e.type
                    const userId = e && e.data && e.data.userId
                    const offer = e && e.data && e.data.offer
                    const answer = e && e.data && e.data.answer
					const nickname = e && e.data && e.data.nickname
					const candidate = e && e.data && e.data.candidate
					if (type === 'join' || type === 'leave') {
						this.$message.success(`${nickname}${type === 'join' ? '加入房间' : '离开房间'}`)
						const params = { "roomId": this.formInline.roomId }
						this.linkSocket.emit('roomUserList', params)
					}
					if (type === 'offer') {
						await this.onRemoteOffer(userId, offer)
					}
					if (type === 'answer') {
						await this.onRemoteAnswer(userId, answer)
					}
					if (type === 'candidate') {
						this.onCandiDate(userId, candidate)
					}
				})
				this.linkSocket.on("error", (e) => {
					console.log("socket error", e)
				})
			},
			initInnerLocalDevice() {
				this.localDevice = {
					audioIn:[],
					videoIn: [],
					audioOut: []
				}
				const constraints = { video:true, audio: true }
				if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
					console.log("浏览器不支持");
					return;
				}
				navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
					stream.getTracks().forEach(trick => {
						trick.stop()
					})
					navigator.mediaDevices.enumerateDevices().then((devices) => {
						devices.forEach((device) => {
							const obj = { id: device.deviceId, kind: device.kind, label: device.label }
							if (device.kind === 'audioinput') {
								if (this.localDevice.audioIn.filter(e => e.id === device.deviceId).length === 0) {
									this.localDevice.audioIn.push(obj)
								}
							} else if (device.kind === 'audiooutput') {
								if(this.localDevice.audioOut.filter(e => e.id === device.deviceId).length === 0){
									this.localDevice.audioOut.push(obj)
								}
							} else if (device.kind === 'videoinput' ) {
								if(this.localDevice.videoIn.filter(e => e.id === device.deviceId).length === 0){
									this.localDevice.videoIn.push(obj)
								}
							}
						});
						console.log('获取设备 => ', this.localDevice)
					}).catch(this.handleMediaError);
				}).catch(this.handleMediaError);
			},
            handleMediaError(error) {
                console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
            },
            getUrlParams(queryName) {
                let url = window.location.href
                let query = decodeURI(url.split('?')[1]);
                let vars = query.split("&");
                for (let i = 0; i < vars.length; i++) {
                    let pair = vars[i].split("=");
                    if (pair[0] === queryName) {
                        return pair[1];
                    }
                }
                return null;
            },
			async setDomVideoStream(domId, newStream) {
                this.$nextTick(() => {
                    let video = this.$refs[domId]
                    let stream = video.srcObject
                    if (stream) {
                        stream.getAudioTracks().forEach( e=>{
                            stream.removeTrack(e)
                        })
                        stream.getVideoTracks().forEach(e=>{
                            stream.removeTrack(e)
                        })
                    }
                    video.srcObject = newStream
                    video.muted = true
                })
			},
			setDomVideoTrack(domId, track) {
                this.$nextTick(() => {
                    let video = this.$refs[domId]
                    let stream = video.srcObject
                    if (stream) {
                        stream.addTrack(track)
                    } else {
                        stream = new MediaStream()
                        stream.addTrack(track)
                        video.srcObject = stream
						video.controls = true
						video.autoplay = true
                        video.muted = false
                    }
                })
			},
			async getUserMedia() {
				const audioId = this.formInline.audioInId
				const videoId = this.formInline.videoId
				const constraints = {
				    audio: { deviceId: audioId ? { exact: audioId } : undefined },
				    video: {
				        deviceId: videoId ? { exact: videoId } : undefined,
				        width: 768,
				        height: 480,
				        frameRate: { ideal: 15, max: 24 }
				    }
				};
				if (window.stream) {
				    window.stream.getTracks().forEach(track => {
				        track.stop();
				    });
				}
			    return await navigator.mediaDevices.getUserMedia(constraints).catch(this.handleMediaError)
			},
			async getShareMedia() {
			    const constraints = {
			        video: { 
						width: 1920,
						height: 1080,
						frameRate: 24,
					},
					audio: true
			    };
			    if (window.stream) {
			        window.stream.getTracks().forEach(track => {
			            track.stop();
			        });
			    }
			    return await navigator.mediaDevices.getDisplayMedia(constraints).catch(this.handleMediaError);
			},
            async initMeetingRoomPc() {
				// 当前是发布者时, 开启视频流并展示
				if (this.formInline.pub === 'pub') {
					console.log('触发233 => ', this.formInline)
					if (!this.localStream) {
						this.localStream = await this.getShareMedia();
						this.setDomVideoStream("localVideoRef", this.localStream)
					}
				}

				// 存储发布者ID
				const localUid = this.formInline.userId
				let publisher = this.roomUserList.filter(e => e.pub === 'pub').map(e => e.userId)
				if (publisher.length > 0) {
					publisher = publisher[0]
					this.publisher = publisher
				} else {
					return;
				}

				// 和发布者建立RTC连接
				let pcKey = `${localUid}-${publisher}`
				let pc = this.rtcPcMaps.get(pcKey)
				if (!pc) {
					const PeerConnection = this.peerConnection
					pc = new PeerConnection(this.rtcPcParams)
					this.rtcPcMaps.set(pcKey, pc)
				}

				/**
				 * 设置 RTC 收发器双向流-传输方向
				 * sendrecv：发送和接收 RTP 数据
				 * sendonly：只发送 RTP 数据，不接收
				 * recvonly：只接收 RTP 数据，不发送
				 * inactive：不发送也不接收 RTP 数据
				 */
				pc.addTransceiver("audio", {direction: "recvonly"});
				pc.addTransceiver("video", {direction: "recvonly"});

				// 监听发布者数据
				this.onPcEvent(pc, localUid, publisher)
				
				// 发布者创建初始信令, 存储描述, 并发送信令给用户
				const offer = await pc.createOffer()
				await pc.setLocalDescription(offer)
				const params = {
					"targetUid": publisher,
					"userId": localUid,
					"offer": offer
				}
				this.linkSocket.emit("offer", params)
			},
			onPcEvent(pc, localUid, remoteUid) {
                // 监听轨道
                pc.ontrack = (e) => {
					console.log('监听发布者视频流 => ', e)
					this.setDomVideoTrack("remoteVideoRef", e.track)
				};

                // 监听网络协商
				pc.onicecandidate = (e) => {
                    if (e.candidate) {
                        const params = {
                            "targetUid": remoteUid,
                            "userId": localUid,
                            "candidate" :e.candidate
                        }
                        this.linkSocket.emit('candidate', params)
                    } else {
                        console.log("监听此次协商中, 没有更多候选了")
                    }
				}
			},
			async onRemoteOffer(fromUid, offer) {
				const localUid = this.formInline.userId

				const pcKey = `${localUid}-${fromUid}`
				const PeerConnection = this.peerConnection
				const pc = new PeerConnection(this.rtcPcParams)
				this.rtcPcMaps.set(pcKey, pc)
				console.log("发布者监听远端 offer");

				// 监听消息通道、轨道信息、网络协商信息
				this.onPcEvent(pc, localUid, fromUid)

				// 发布者RTC添加轨道数据
				for (const track of this.localStream.getTracks()) {
				    pc.addTrack(track);
				}

				// 设置描述, 发送应答信息
				pc.setRemoteDescription(offer)
				const answer = await pc.createAnswer();
				await pc.setLocalDescription(answer);
				const params = {
					"targetUid": fromUid,
					"userId": localUid,
					"answer": answer
				}
				this.linkSocket.emit("answer", params)
			},
			async onRemoteAnswer(fromUid, answer) {
				const localUid = this.formInline.userId
				const pcKey = `${localUid}-${fromUid}`
				const pc = this.rtcPcMaps.get(pcKey)
				await pc.setRemoteDescription(answer);
			},
			onCandiDate(fromUid, candidate){
				let pc = this.rtcPcMaps.get(`${this.formInline.userId}-${fromUid}`)
				pc.addIceCandidate(candidate)
			}
		}
	}
</script>

<style scoped lang="less">
    .container {
        width: 98%;
        height: 98vh;
        margin-top: 30px;
        .local-video {
            width: 300px;
            height: 200px;
        }
        .remote-video {
            width: 300px;
            height: 200px;
        }
    }
</style>